package jmetal.util.dir;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.List;
import java.util.StringTokenizer;

import jmetal.core.Solution;
import jmetal.core.SolutionSet;

public class DIRCompute {

	private String functionType_;
	private double zp_[];
	private double mp_[];
	public DIRCompute(){
		functionType_ = "_PBI";
		
	}

	
	public int getMinDecSol(SolutionSet allfront,SolutionSet front,int populationSize){
		double maxDir = 0;
		int maxSub = 0;
		for(int i =0;i<front.size();i++){//i：当前去除的解
			SolutionSet compSet = new SolutionSet();
			compSet.addAll(allfront, 0, allfront.size());
			if(i>0)
				compSet.addAll(front, 0, i);
			compSet.addAll(front, i+1, front.size());
			double dir = computeDIR(compSet,populationSize);
			if(dir>maxDir){ //去除该解导致DIR最大，应保留
				maxSub = i;
				maxDir = dir;
			}
		}
		//System.out.println(maxDir);
		return maxSub;
		
	}

	/**
	 * 计算DIR
	 * @param compSet
	 * @param populationSize
	 * @return
	 */
	public double computeDIR(SolutionSet compSet,int populationSize) {
		double[][] vectors = 
				getReferenceVectors(compSet.get(0).getNumberOfObjectives(),populationSize);
		int[] counts = getSdArray(compSet, vectors);
		
		double dir = standardDeviation(counts);
		double maxDir = (double)(vectors.length)/(double)(compSet.size())*Math.sqrt((double)compSet.size());
		return (dir/maxDir);
	}

	
	/**
	 * 获取基于PBI值的Evaluation Function
	 * @param compSet
	 * @param vectors
	 * @return
	 */
	public double[] getPBIArray(SolutionSet compSet,double[][] vectors){
		zp_ = new double[compSet.get(0).getNumberOfObjectives()];
		setIdealPoint(compSet,zp_);
		double [] PBIArray = new double[compSet.size()];
		double [] counts = new double[compSet.size()];
		
		for(int i = 0;i<PBIArray.length;i++)
			PBIArray[i] = 0;
		for(int i = 0;i<vectors.length;i++){
			if(zero(vectors[i]))
				continue;
			
			int minSub = 0;
			double minAngle = 0;
			
			for(int j = 0;j<compSet.size();j++){
				Solution indiv = compSet.get(j);
				double[] indivObj = new double[vectors[0].length];
				for(int k = 0;k<vectors[0].length;k++)
					indivObj[k] = indiv.getObjective(k);
				double innerProd = innerProduct(vectors[i], indivObj);
				double tempAngle = innerProd/ (norm(vectors[i])*norm(compSet.get(j).getObjectives()));
				if(tempAngle>minAngle){
					minAngle = tempAngle;
					minSub = j;
				}
			}
			
//			if(minSub==0){
//				double iii = 1/fitness(compSet.get(minSub),vectors[i]);
//				System.out.println(iii);
//			}
			PBIArray[minSub] += 1/normFitness(compSet.get(minSub));
			counts[minSub]++;
		}
		return PBIArray;
	}

	private double normFitness(Solution solution) {
		double [] indivObj = new double[solution.getNumberOfObjectives()];
		for(int i = 0;i<indivObj.length;i++)
			indivObj[i] = solution.getObjective(i)-zp_[i];
		return normVector(indivObj);
	}


	private boolean zero(double[] ds) {
		for(int i =0;i<ds.length;i++){
			if(ds[i]!=0){
				return false;
			}
		}
		return true;
	}


	/**
	 * 设置ideal point
	 * @param compSet
	 * @param zp
	 */
	private void setIdealPoint(SolutionSet compSet, double[] zp) {
		for(int i = 0;i<zp.length;i++){
			zp[i] = Double.MAX_VALUE;
			for(int j = 0;j<compSet.size();j++){
				if(compSet.get(j).getObjective(i)<zp[i])
					zp[i] = compSet.get(j).getObjective(i);
			}
		}
		
	}


	/**
	 * 计算内积
	 * @param vector
	 * @param indivObj
	 * @return
	 */
	public double innerProduct(double[] vector, double[] indivObj) {
		double innerProd = 0;
		for(int dim = 0;dim<vector.length;dim++){
			double x1 = vector[dim];
			double x2 = indivObj[dim];
			innerProd =innerProd + (x1*x2);
		}
		return innerProd;
	}
	
	/**
	 * 计算解的适应值
	 * @param indiv
	 * @param lambda
	 * @return
	 */
	private double fitness(Solution indiv, double[] lambda) {
		int dim = indiv.getNumberOfObjectives();
		double fitness;
		fitness = 0.0;

		if (functionType_.equals("_TCHE1")) {
			double maxFun = -1.0e+30;

			for (int n = 0; n < dim; n++) {
				double diff = Math.abs(indiv.getObjective(n) - zp_[n]);

				double feval;
				if (lambda[n] == 0) {
					feval = 0.0001 * diff;
				} else {
					feval = diff * lambda[n];
				}
				if (feval > maxFun) {
					maxFun = feval;
				}
			} // for

			fitness = maxFun;
		} else if (functionType_.equals("_TCHE2")) {
			double maxFun = -1.0e+30;

			for (int i = 0; i < dim; i++) {
				double diff = Math.abs(indiv.getObjective(i) - zp_[i]);

				double feval;
				if (lambda[i] == 0) {
					feval = diff / 0.000001;
				} else {
					feval = diff / lambda[i];
				}
				if (feval > maxFun) {
					maxFun = feval;
				}
			} // for
			fitness = maxFun;
		} else if (functionType_.equals("_PBI")) {
			double theta; // penalty parameter
			theta = 1;

			// normalize the weight vector (line segment)
			double nd = normVector(lambda);
			for (int i = 0; i < dim; i++)
				lambda[i] = lambda[i] / nd;

			double[] realA = new double[dim];
			double[] realB = new double[dim];

			// difference between current point and reference point
			for (int n = 0; n < dim; n++)
				realA[n] = (indiv.getObjective(n) - zp_[n]);

			// distance along the line segment
			double d1 = Math.abs(innerProduct(realA, lambda));

			// distance to the line segment
			for (int n = 0; n < dim; n++)
				realB[n] = (indiv.getObjective(n) - (zp_[n] + d1 * lambda[n]));
			double d2 = normVector(realB);

			fitness = d1 + theta * d2;
		} else {
			System.out.println("ANSGAII.fitnessFunction: unknown type "
					+ functionType_);
			System.exit(-1);
		}
		return fitness;
	}
	
	
	public double normVector(double[] z) {
		double sum = 0;

		for (int i = 0; i < z.length; i++)
			sum += z[i] * z[i];

		return Math.sqrt(sum);
	}

	/**
	 * 获取绑定参考线个数数组
	 * @param compSet
	 * @param vectors
	 * @return
	 */
	public int[] getSdArray(SolutionSet compSet, double[][] vectors) {
		//设置最小角度，应抽离
		for(int i=0;i<compSet.size();i++){
			compSet.get(i).setMinAngle(0);
		}
		
		for(int i = 0;i<compSet.size();i++){
			compSet.get(i).setMinDistance(Double.MAX_VALUE);
		}
		
		int [] counts = new int[compSet.size()];
		for(int i = 0;i<counts.length;i++)
			counts[i] = 0;
		
		for(int i = 0;i<vectors.length;i++){
			int minSub = 0;
			double minAngle = 0;
			
			//
			double minDistance = Double.MAX_VALUE;
			int minDisSub = 0;
			
			for(int j = 0;j<compSet.size();j++){
				double[] objectives = compSet.get(j).getObjectives();
//				objectives = adaptObjectives(objectives);
				double innerProd = 0;
				for(int dim = 0;dim<vectors[i].length;dim++){
					double x1 = vectors[i][dim];
					double x2 = objectives[dim];
					innerProd =innerProd + (x1*x2);
				}
				double tempAngle = innerProd/ (norm(vectors[i])*norm(objectives));
				//
				double sin = Math.sqrt(1-tempAngle*tempAngle);
				double tempDistance = norm(objectives)*sin;
				
				if(tempDistance<minDistance){
					minDistance = tempDistance;
					minDisSub = j;
				}
					
				if(tempAngle>minAngle){
					minAngle = tempAngle;
					minSub = j;
				}
			}
			
			//
			if(compSet.get(minDisSub).getMinDistance()>minDistance)
				compSet.get(minDisSub).setMinDistance(minDistance);
			
			
			//为解设置最近的角度值,应抽离
//			if(compSet.get(minSub).getMinAngle()<minAngle)
//				compSet.get(minSub).setMinAngle(minAngle);
			double angle = Math.acos(minAngle);
			compSet.get(minSub).setMinAngle(compSet.get(minSub).getMinAngle()+angle);
			
			counts[minSub]++;
		}
		return counts;
	}

	private double[] adaptObjectives(double[] objectives) {
		double[] newObjectives = new double[objectives.length];
		double sum = 0.0;
		for(int i = 0;i<objectives.length;i++){
			sum+= objectives[i];
		}
		for(int i = 0;i<objectives.length;i++){
			newObjectives[i] = objectives[i]/sum;
		}
		return newObjectives;
	}


	private double standardDeviation(int[] counts) {
		double sum = 0;
		for(int i = 0;i<counts.length;i++)
			sum+=counts[i];
		double mean = sum/counts.length;
		double sum_sd = 0;
		for(int i = 0;i<counts.length;i++){
			sum_sd =sum_sd+(counts[i]-mean)*(counts[i]-mean); 
		}
		return Math.sqrt(sum_sd/counts.length);
	}

	private double norm(double[] objectives) {
		double norm = 0;
		for(int i = 0;i<objectives.length;i++){
			norm=norm+objectives[i]*objectives[i];
		}
		return Math.sqrt(norm);
	}

	private double[][] getReferenceVectors(int objNum,int populationSize) {

		double[][] vectors = new double[populationSize][objNum];
		
		String dataFileName;
		dataFileName = "W" + objNum + "D_" + populationSize + ".dat";

		try {
			// Open the file
			FileInputStream fis = new FileInputStream(dataFileName);
			InputStreamReader isr = new InputStreamReader(fis);
			BufferedReader br = new BufferedReader(isr);

//			int numberOfObjectives = 0;
			int i = 0;
			int j = 0;
			String aux = br.readLine();
			while (aux != null) {
				StringTokenizer st = new StringTokenizer(aux);
				j = 0;
//				numberOfObjectives = st.countTokens();
				while (st.hasMoreTokens()) {
					double value = (new Double(st.nextToken())).doubleValue();
					vectors[i][j] = value;
					// System.out.println("lambda["+i+","+j+"] = " + value) ;
					j++;
				}
				aux = br.readLine();
				i++;
			}
			br.close();
		} catch (Exception e) {
			System.out
					.println("initUniformWeight: failed when reading for file: "
							+ "/" + dataFileName);
			e.printStackTrace();
		}

		return vectors;
	}

	
	
	public int[] getDirArray(SolutionSet population, SolutionSet front,
			int populationSize) {
		SolutionSet compSet = new SolutionSet();
		compSet.addAll(population, 0, population.size());
		compSet.addAll(front, 0, front.size());
		double[][] vectors = 
				getReferenceVectors(compSet.get(0).getNumberOfObjectives(),populationSize);
		int[] counts = getSdArray(compSet, vectors);
		int[] frontCounts = new int[front.size()];
		System.arraycopy(counts, population.size(), frontCounts, 0, front.size());
		
		return frontCounts;
	}
	
	public double[] getOccupyPBI(SolutionSet population, SolutionSet front,
			int populationSize) {
		SolutionSet compSet = new SolutionSet();
		compSet.addAll(population, 0, population.size());
		compSet.addAll(front, 0, front.size());
		double[][] vectors = getReferenceVectors(compSet.get(0)
				.getNumberOfObjectives(), populationSize);
		double[] pbiArray = getPBIArray(compSet, vectors);
		double[] frontPbis = new double[front.size()];
		System.arraycopy(pbiArray, population.size(), frontPbis, 0,
				front.size());

		return frontPbis;
	}


	/**
	 * set DIR occupy counts for each solution
	 * @param population
	 */
	public void assessDir(SolutionSet population,int populationSize) {
//		zp_ = new double[population.get(0).getNumberOfObjectives()];
//		setIdealPoint(population, zp_);
//		mp_ = new double[population.get(0).getNumberOfObjectives()];
//		setMaxPoint(population,mp_);
		double[][] vectors = getReferenceVectors(population.get(0).getNumberOfObjectives(), populationSize);
//		vectors = adaptVectors(vectors,new double[]{0,0,2.5},new double[]{1,1,6});
		
		int[] countArray = getSdArray(population, vectors);
		for(int i = 0;i<countArray.length;i++){
			population.get(i).setCountOfOccupyVectors(countArray[i]);
		}
	}



	private double[][] adaptVectors(double[][] vectors, double[] zp) {
		for(int i = 0;i<vectors.length;i++){
			for(int dim = 0;dim<vectors[i].length;dim++){
				vectors[i][dim] = vectors[i][dim]-zp[dim];
			}
		}
		return vectors;
	}


	/**
	 * 调整参考线
	 * @param vectors
	 * @param zp
	 * @param mp
	 * @return 
	 */
	private double[][] adaptVectors(double[][] vectors, double[] zp, double[] mp) {
		for(int i = 0;i<vectors.length;i++){
			for(int dim = 0;dim<vectors[i].length;dim++){
				vectors[i][dim] = zp[dim]+(mp[dim]-zp[dim])*vectors[i][dim];
			}
		}
		return vectors;
		
	}


	/**
	 * 设置最大点
	 * @param population
	 * @param mp
	 */
	private void setMaxPoint(SolutionSet population, double[] mp) {
		for(int i = 0;i<mp.length;i++){
			mp[i] = 0;
			for(int j = 0;j<population.size();j++){
				if(population.get(j).getObjective(i)>mp[i])
					mp[i] = population.get(j).getObjective(i);
			}
		}
		
	}


	/**
	 * set the Min Angle between the solution to vectors
	 * @param worstSet
	 * @param size
	 */
	public void assessMinAngle(List<Solution> worstSet, int size) {
		double[][] vectors = getReferenceVectors(worstSet.get(0).getNumberOfObjectives(), size);
		for(Solution solution:worstSet){
		    double minAngle = 0;//余弦值,越大角越小
			for(int i = 0;i<vectors.length;i++){
				double innerProd = 0;
				for(int dim = 0;dim<vectors[i].length;dim++){
					double x1 = vectors[i][dim];
					double x2 = solution.getObjective(dim);
					innerProd =innerProd + (x1*x2);
				}
				double tempAngle = innerProd/ (norm(vectors[i])*norm(solution.getObjectives()));
				if(tempAngle>minAngle){
					minAngle = tempAngle;
				}
			}
			solution.setMinAngle(minAngle);
		}
	}


	/**
	 * 为每个解设置最近解距离
	 * @param worstSet
	 * @param aux
	 * @param size
	 */
	 public void assessNearest(SolutionSet worstSet, SolutionSet aux) {
		for(int i = 0;i<worstSet.size();i++){
			Solution indiv = worstSet.get(i);
//			double minDis = Double.MAX_VALUE;
			double minAngle = 0;
			for(int j = 0;j<aux.size();j++){
				Solution indiv2 = aux.get(j);
				if (indiv2 != indiv) {
					double innerProd = innerProduct(indiv.getObjectives(), indiv2.getObjectives());
					double tempAngle = innerProd/ (norm(indiv.getObjectives())*norm(indiv2.getObjectives()));
					if(tempAngle>minAngle)
						minAngle = tempAngle;
					
//					double[] disVec = new double[indiv.getNumberOfObjectives()];
//					for (int k = 0; k < indiv.getNumberOfObjectives(); k++) {
//						disVec[k] = indiv2.getObjective(k) - indiv.getObjective(k);
//					}
//					double distance = norm(disVec);
//					if (distance < minDis)
//						minDis = distance;
				}
			}
			indiv.setNearestSolDis(minAngle);
		}
		
	}
}
